/****************************************************************************
* Copyright (c) 2006 Jeremy Dowdall
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*    Jeremy Dowdall <jeremyd@aspencloud.com> - initial API and implementation
*****************************************************************************/

package org.aspencloud.widgets.cnumpad;

import org.aspencloud.widgets.ACW;
import org.aspencloud.widgets.DropCombo;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Widget;


public class CNumPadCombo extends DropCombo {

	// Integer Formats
	public static final String SSN 		= "###-##-####";
	public static final String PHONE1 	= "###-###-####";
	public static final String PHONE2 	= "(###) ###-####";
	public static final String PHONE3 	= "#-###-###-####";
	
	private static final int SELECT_ALL = -2;
	private static final int SELECT_NONE = -1;
	private static final int SELECT_NORMAL = 0;

	private CNumPad numpad;
	
	private Point selection = new Point(0,0);
	private int selectionState = SELECT_NORMAL;
	boolean rightClick = false;

	private String pattern;
	private String buffer = "";
	private int maxLen;
	private int[] bufferToPattern;
	private int[] patternToBuffer;
	
	private Point tmpSelection;
	private String tmpBuffer;
	private boolean cancelFlag = false;

	private Listener textListener = new Listener() {
		public void handleEvent(Event event) {
			switch (event.type) {
			case SWT.MouseDown:
				if(event.button == 3) {
					rightClick = true;
				}
				break;
			case SWT.Paint:
				if(hasFocus && (!text.isFocusControl())) {
					Point sel = text.getSelection();
					if(sel.x == sel.y) {
						if(ACW.gtk) {
							// gtk cannot use getCaretLocation() with text style = SWT.SINGLE
							//	SWT is hardcoded to always return new Point(0,0) (gtk only) ?
								Point strSize = event.gc.textExtent(text.getText(0, text.getCaretPosition()-1));
								event.gc.drawLine(strSize.x+1, 2, strSize.x+1, strSize.y + 1);
						} else {
						// due to Windows bugs, SWT has fudged getCaretLocation() (see SWT code)
						//  if there are performance problems - find another work-around
							Point strLoc = text.getCaretLocation();
							int height = text.getLineHeight();
							event.gc.drawLine(strLoc.x, strLoc.y, strLoc.x, strLoc.y+height-1);
						}
					} else {
						if(!ACW.gtk) { // gtk does this for us
							Point p1 = event.gc.textExtent(text.getText(0, sel.x-1));
							Point p2 = event.gc.textExtent(text.getSelectionText());
							event.gc.setBackground(event.display.getSystemColor(SWT.COLOR_GRAY));
							event.gc.fillRectangle(p1.x+3, 0, p2.x-1, p1.y-1);
							event.gc.drawText(text.getSelectionText(), p1.x+3, 0);
						}
					}
				}
			case SWT.Verify:
				verify(event);
				break;
			}
		}
	};
	
	
	public CNumPadCombo(Composite parent, int style)  {
		this(parent, style, null);
	}
	public CNumPadCombo(Composite parent, int style, String pattern)  {
		super(parent, style);

		setPattern(pattern);
		if(pattern == null) {
			setPattern(PHONE1);
		}
		
		if((style & SWT.SIMPLE) != 0) {
//			setButtonVisibility(NEVER);
		} else {
			setContent(numpad = new CNumPad(getPopup(), style));
			numpad.setBackground(getDisplay().getSystemColor(SWT.COLOR_WHITE));
		}
		
		updateText();
		
		text.addListener(SWT.MouseDown, textListener);
		text.addListener(SWT.Verify, textListener);
		text.addListener(SWT.Paint, textListener);
	}
	
	protected void contentsEvents(Event event) {
		if(isPopped()) {
			if(SWT.Selection == event.type) {
				switch (event.detail) {
				case CNumPad.FUNC_ACCEPT:
					cancelFlag = false;
					popUp(false);
					break;
				case CNumPad.FUNC_CANCEL:
					popUp(false);
					break;
				case CNumPad.FUNC_CLR:
					buffer = "";
					selection.x = selection.y = 0;
					updateText();
					break;
				case CNumPad.FUNC_BKSP:
				case CNumPad.KEYPAD:
					verify(event);
					break;
				}
				return;
			} else {
				super.contentsEvents(event);
			}
		}
	}
	
	private String format() {
		char[] ba = buffer.toCharArray();
		char[] pa = pattern.toCharArray();
		char[] out = new char[pa.length];
		for(int i = 0, j = 0; (i < pa.length) && (j < ba.length); i++) {
			if(pa[i] == '#') {
				out[i] = ba[j++];
				if(j == maxLen) {
					while(++i < pa.length) {
						out[i] = pa[i];
					}
				}
			} else {
				out[i] = pa[i];
			}
		}
		selection.x = bufferToPattern[(selection.x >= 0) ? ((selection.x < bufferToPattern.length) ? selection.x : 0) : 0];
		selection.y = bufferToPattern[(selection.y >= 0) ? ((selection.y < bufferToPattern.length) ? selection.y : 0) : 0];
		return new String(out).trim();
	}

	protected void handleFocus(int type, Widget widget) {
		if(isDisposed()) return;
		
		if(SWT.FocusIn == type) {
			if(!hasFocus) {
				rightClick = false;
				selectionState = SELECT_ALL;
				updateText();
			}
		}

		super.handleFocus(type, widget);

		if(SWT.FocusOut == type) {
			if(!hasFocus) {
				if(!rightClick) {
					selectionState = SELECT_NONE;
					updateText();
				}
			}
		}
	}

	private String parse(String formattedStr) {
		char[] fa = formattedStr.toCharArray();
		char[] out = new char[fa.length];
		int count = 0;
		for(int i = 0; i < fa.length; i++) {
			if(Character.isDigit(fa[i])) {
				out[count++] = fa[i];
			}
		}
		selection.x = patternToBuffer[(selection.x >= 0) ? ((selection.x < patternToBuffer.length) ? selection.x : 0) : 0];
		selection.y = patternToBuffer[(selection.y >= 0) ? ((selection.y < patternToBuffer.length) ? selection.y : 0) : 0];
		return new String(out).trim();
	}

	public void popUp(boolean pop) {
		if(pop) {
			tmpBuffer = buffer;
			tmpSelection = selection;
			cancelFlag = true;
		} else if(cancelFlag){
			buffer = tmpBuffer;
			selection = tmpSelection;
			updateText();
			cancelFlag = false;
		}
		super.popUp(pop);
		if(!ACW.gtk) {
			text.redraw();
			text.update();
		}
	}
	
	protected void setModifyEventProperties(Event e) {
		e.data = buffer;
	}
	
	/**
	 * example: "(###) ###-####" is a form of a North American Phone Number
	 * <p>
	 * <dl>
	 * <dt>#</dt>
	 * <dd>digit (zero is not shown)</dd>
	 * <dt>any other character</dt>
	 * <dd>shown as text if bordering digits are shown</dd>
	 * </p>
	 * @param pattern
	 */
	public void setPattern(String pattern) {
		if(pattern == null) return;
		
		int count = 0;
		char[] pa = pattern.toCharArray();
		for(int i = 0; i < pa.length; i++) {
			if(pa[i] == '#') count++;
		}
		if(count == 0) return;
		
		this.pattern = pattern;
		maxLen = count;

		bufferToPattern = new int[maxLen+1];
		patternToBuffer = new int[pattern.length()+1];
		
		count = 0;
		for(int i = 0; i < pa.length; i++) {
			patternToBuffer[i] = count;
			if(pa[i] == '#') {
				bufferToPattern[count++] = i;
			}
		}
		bufferToPattern[maxLen] = pattern.length();
		patternToBuffer[pattern.length()] = maxLen;

		if(buffer.length() > maxLen) buffer = buffer.substring(0, maxLen);
		selection.x = selection.y = 0;
		updateText();
	}
	
	/**
	 * Set the text of this NumPadCombo
	 * <p>
	 * The input string is formatted (everything but digits removed)
	 * and then displayed according to the currently applied pattern
	 * </p>
	 * @param string
	 */
	public void setText(String string) {
		text.removeListener(SWT.Verify, textListener);
		setBuffer(string);
		text.setText(format());
		text.selectAll();
		selection.x = 0;
		selection.y = string.length();
		selectionState = SELECT_ALL;
		layout(true, true);
		text.addListener(SWT.Verify, textListener);
	}
	
	private void updateText() {
		final String output = format();

		getDisplay().asyncExec(new Runnable() {
			public void run() {
				if((text != null) && (!text.isDisposed())) {
					if(!output.equals(text.getText())) {
						text.removeListener(SWT.Verify, textListener);
						text.setText(output);
						text.addListener(SWT.Verify, textListener);
					}
					switch(selectionState) {
					case SELECT_ALL:
						text.selectAll();
						break;
					case SELECT_NONE:
						text.clearSelection();
						break;
					default:
						text.setSelection(selection);
					}
					selectionState = SELECT_NORMAL;
				}
			}
		});
	}
	
	private void verify(Event e) {
		e.doit = false;

		switch (e.keyCode) {
		case SWT.DEL:
			String str = text.getText();
			if(str.length() > 0) {
				selection = text.getSelection();
				if(selection.x < str.length()) {
					buffer = parse(str);
					if(selection.x == selection.y) {
						buffer = buffer.substring(0, selection.x) + buffer.substring(selection.x + 1);
					} else {
						buffer = buffer.substring(0, selection.x) + buffer.substring(selection.y);
					}
					updateText();
				}
			}
			break;
		case '\b':
			str = text.getText();
			if(str.length() > 0) {
				selection = text.getSelection();
				if(patternToBuffer[selection.x] > 0) {
					buffer = parse(str);
					if(selection.x == selection.y) {
						buffer = buffer.substring(0, selection.x-1) + buffer.substring(selection.x);
						selection.x--;
						selection.y--;
					} else {
						buffer = buffer.substring(0, selection.x) + buffer.substring(selection.y);
						selection.y = selection.x;
					}
					updateText();
				}
			}
			break;
		default:
			if(e.text == null) return;
		
			str = text.getText();
			selection = text.getSelection();
			if((selection.x == selection.y) && (buffer.length() == maxLen)) return;
			
			char c = e.character;
			if(	((e.text.length() == 1) && String.valueOf(c).equals(e.text) && Character.isDigit(c))
					|| (e.text.length() > 1) ) {
				if(e.text.length() == 1) {
					buffer = parse(str);
					if(selection.x == selection.y) {
						buffer = buffer.substring(0, selection.x) + e.text + buffer.substring(selection.y);
						selection.x++;
						selection.y++;
					} else {
						buffer = buffer.substring(0, selection.x) + e.text + buffer.substring(selection.y);
						selection.x++;
						selection.y = selection.x;
					}
					updateText();
				} else if(e.text.length() > 1){
					setBuffer(e.text);
					updateText();
				}
			}
		}
	}
	
	private void setBuffer(String string) {
		String str = parse(string);
		int n0 = str.length(); // what's going in
		int n1 = selection.y - selection.x;  // what's coming out
		int n2 = buffer.length(); // subject
		if((n0 - n1 + n2) > maxLen) { // truncate - msb on right
			str = str.substring(str.length()-maxLen);
		}
		buffer = buffer.substring(0, selection.x) + str + buffer.substring(selection.y);
	}
}
